#include "NetBIOS.h"

#include <functional>

#define NBNS_PORT 137
#define NBNS_MAX_HOSTNAME_LEN 32

typedef struct {
    uint16_t id;
    uint8_t flags1;
    uint8_t flags2;
    uint16_t qcount;
    uint16_t acount;
    uint16_t nscount;
    uint16_t adcount;
    uint8_t name_len;
    char name[NBNS_MAX_HOSTNAME_LEN + 1];
    uint16_t type;
    uint16_t clas;
} __attribute__((packed)) nbns_question_t;

typedef struct {
    uint16_t id;
    uint8_t flags1;
    uint8_t flags2;
    uint16_t qcount;
    uint16_t acount;
    uint16_t nscount;
    uint16_t adcount;
    uint8_t name_len;
    char name[NBNS_MAX_HOSTNAME_LEN + 1];
    uint16_t type;
    uint16_t clas;
    uint32_t ttl;
    uint16_t data_len;
    uint16_t flags;
    uint32_t addr;
} __attribute__((packed)) nbns_answer_t;

static void _getnbname(const char *nbname, char *name, uint8_t maxlen){
    uint8_t b;
    uint8_t c = 0;

    while ((*nbname) && (c < maxlen)) {
        b = (*nbname++ - 'A') << 4;
        c++;
        if (*nbname) {
            b |= *nbname++ - 'A';
            c++;
        }
        if(!b || b == ' '){
            break;
        }
        *name++ = b;
    }
    *name = 0;
}

static void append_16(void * dst, uint16_t value){
    uint8_t * d = (uint8_t *)dst;
    *d++ = (value >> 8) & 0xFF;
    *d++ = value & 0xFF;
}

static void append_32(void * dst, uint32_t value){
    uint8_t * d = (uint8_t *)dst;
    *d++ = (value >> 24) & 0xFF;
    *d++ = (value >> 16) & 0xFF;
    *d++ = (value >> 8) & 0xFF;
    *d++ = value & 0xFF;
}

void NetBIOS::_onPacket(AsyncUDPPacket& packet){
    if (packet.length() >= sizeof(nbns_question_t)) {
        nbns_question_t * question = (nbns_question_t *)packet.data();
        if (0 == (question->flags1 & 0x80)) {
            char name[ NBNS_MAX_HOSTNAME_LEN + 1 ];
            _getnbname(&question->name[0], (char *)&name, question->name_len);
            if (_name.equals(name)) {
                nbns_answer_t nbnsa;
                nbnsa.id = question->id;
                nbnsa.flags1 = 0x85;
                nbnsa.flags2 = 0;
                append_16((void *)&nbnsa.qcount, 0);
                append_16((void *)&nbnsa.acount, 1);
                append_16((void *)&nbnsa.nscount, 0);
                append_16((void *)&nbnsa.adcount, 0);
                nbnsa.name_len = question->name_len;
                memcpy(&nbnsa.name[0], &question->name[0], question->name_len + 1);
                append_16((void *)&nbnsa.type, 0x20);
                append_16((void *)&nbnsa.clas, 1);
                append_32((void *)&nbnsa.ttl, 300000);
                append_16((void *)&nbnsa.data_len, 6);
                append_16((void *)&nbnsa.flags, 0);
                nbnsa.addr = WiFi.localIP();
                _udp.writeTo((uint8_t *)&nbnsa, sizeof(nbnsa), packet.remoteIP(), NBNS_PORT);
            }
        }
    }
}

NetBIOS::NetBIOS(){

}
NetBIOS::~NetBIOS(){
    end();
}

bool NetBIOS::begin(const char *name){
    _name = name;
    _name.toUpperCase();

    if(_udp.connected()){
        return true;
    }

    _udp.onPacket([](void * arg, AsyncUDPPacket& packet){ ((NetBIOS*)(arg))->_onPacket(packet); }, this);
    return _udp.listen(NBNS_PORT);
}

void NetBIOS::end(){
    if(_udp.connected()){
        _udp.close();
    }
}

#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_NETBIOS)
NetBIOS NBNS;
#endif

// EOF
